package models;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.OneToOne;

import play.data.validation.Required;
import play.db.jpa.JPABase;
import play.db.jpa.Model;

@Entity
public class ServiceReoccurrenceData extends Model
{
	// the values for periods for re-occurrence data are
	// limited.
	public enum Period {
		never, daily, weekly
	}
	
	public enum EndLimit {
		never, occurrenceCount, on
	};
	
	@Required
	public Period period = Period.never;
	
	public int repeatEveryNDay = 0;
	public int repeatEveryNWeek = 0;
	
	// occurs on days?
	public boolean mon;
	public boolean tue;
	public boolean wed;
	public boolean thu;
	public boolean fri;
	public boolean sat;
	public boolean sun;
	
	// whether or not the service is limited by time.
	@Required
	public EndLimit ends = EndLimit.never;
	
	// occurrence count for the service.
	public int occurrenceCount = 1;
	
	// end date value for the session.
	public Date endsAt;
	
	@Override
	public <T extends JPABase> T save() {
		// make sure that restrictions are met.
		
		// if the service won't be repeated;
		if (Period.never.equals(this.period)) {
			// make sure that days and weeks won't be
			// set as repeating.
			this.repeatEveryNDay = 0;
			this.repeatEveryNWeek = 0;
		}
		
		// if the service will repeat daily;
		else if (Period.daily.equals(this.period)) {
			// make sure that weeks are not set as
			// repeating.
			this.repeatEveryNWeek = 0;
		}
		
		// if the service will repeat weekly;
		else if (Period.weekly.equals(this.period)) {
			// make sure that days are not set as
			// repeating.
			this.repeatEveryNDay = 0;
		}
		
		// run the save method of the parent.
		super.save();
		
		// return the current instance for making method
		// chaining possible.
		return (T) this;
	}
	
	public int getSelectedDaysCountInAWeek() {
		int count = 0;
		if (this.mon) count ++;
		if (this.tue) count ++;
		if (this.wed) count ++;
		if (this.thu) count ++;
		if (this.fri) count ++;
		if (this.sat) count ++;
		if (this.sun) count ++;
		
		return count;
	}
	
	public static void createAndAttachSessions(Service service) {
		if (null == service.reoccurrenceData) {
			return;
		}
		
		// fetch the re-occurrence data.
		ServiceReoccurrenceData data = service.reoccurrenceData;
		
		// if there is no re-occurrence;
		if (Period.never.equals(data.period)) {
			// no need to create sessions; so do nothing.
			return;
		}
		
		// if there is re-occurrence based on days;
		else if (Period.daily.equals(data.period)) {
			// if the occurrence count is defined;
			if (EndLimit.occurrenceCount.equals(data.ends)) {
				// for each occurrence;
				for (int i = 0; i < data.occurrenceCount; i++) {
					// create and add the session to the service.
					service.sessions.add(Service.createSessionFromService(service, i));
				}
			}
			
			// if the occurrence ends on a specific date;
			else {
				if (EndLimit.never.equals(data.ends)) {
					// set as ending in 2 years.
					data.endsAt = addYearsToDate(service.startsAt, 2);
				}
				
				// set an index for the date to be incremented.
				Date dateIndex = service.startsAt;
				
				// while the date does not pass the limit;
				int i = 0;
				while (data.endsAt.compareTo(dateIndex) >= 0) {
					// create the session.
					Service session = Service.createSessionFromService(service, i++);
					
					//add the session to the service.
					service.sessions.add(session);
					
					// increment the dateIndex.
					dateIndex = session.endsAt;
				}
			}
		}
		
		// if there is re-occurrence based on weeks;
		else if (Period.weekly.equals(data.period)) {
			// get the first day of the session.
			Calendar gregCal = new GregorianCalendar();
			gregCal.setFirstDayOfWeek(GregorianCalendar.MONDAY);
			gregCal.setTime(service.startsAt);
			
			// if the occurrence count is defined;
			if (EndLimit.occurrenceCount.equals(data.ends)) {
				for (int i = 0; i < data.occurrenceCount; i++) {
					// create the weekly schedule.
					if (0 == i) {
						createWeeklySchedule(service, gregCal.get(Calendar.DAY_OF_WEEK), i, gregCal.get(Calendar.DAY_OF_WEEK));
					} else {
						createWeeklySchedule(service, 1, i, gregCal.get(Calendar.DAY_OF_WEEK));
					}
				}
			}
			
			// if the occurrence ends on a specific date;
			else {
				if (EndLimit.never.equals(data.ends)) {
					// set as ending in 2 years.
					data.endsAt = addYearsToDate(service.startsAt, 2);
				}
				
				// set an index for the date to be incremented.
				Date dateIndex = service.startsAt;
				
				// while the date does not pass the limit;
				int i = 0;
				while (data.endsAt.compareTo(dateIndex) >= 0) {
					int dayOfWeek = gregCal.get(Calendar.DAY_OF_WEEK);
					createWeeklySchedule(service, dayOfWeek, i++, gregCal.get(Calendar.DAY_OF_WEEK));
					
					// increment the dateIndex.
					if (0 < service.sessions.size()) {
						dateIndex = service.sessions.get(service.sessions.size() - 1).endsAt;
					} else {
						// again a bad hack.
						dateIndex = service.endsAt;
					}
				}
			}
		}
	}
	
	public static Date addYearsToDate(Date date, int numberOfYearsToBeAdded) {
		// fetch the Calendar instance.
		Calendar cal = Calendar.getInstance();

		// set the calendar.
		cal.setTime(date);
		
		// add days.
		cal.add(Calendar.YEAR, numberOfYearsToBeAdded);

		// return the new date.
		return cal.getTime();
	}
	
	public static Date addDaysToDate(Date date, int numberOfDaysToBeAdded) {
		// fetch the Calendar instance.
		Calendar cal = Calendar.getInstance();

		// set the calendar.
		cal.setTime(date);
		
		// add days.
		cal.add(Calendar.DATE, numberOfDaysToBeAdded);

		// return the new date.
		return cal.getTime();
	}
	
	public static Date addHoursToDate(Date date, int numberOfHoursToBeAdded) {
		// fetch the Calendar instance.
		Calendar cal = Calendar.getInstance();

		// set the calendar.
		cal.setTime(date);
		
		// add days.
		cal.add(Calendar.HOUR_OF_DAY, numberOfHoursToBeAdded);

		// return the new date.
		return cal.getTime();
	}
	
	public static void createWeeklySchedule(Service service, int dayOfWeek, int weekIndex, int realDayOfWeek) {
		if (service.reoccurrenceData.sun && GregorianCalendar.SUNDAY >= dayOfWeek) {
			service.sessions.add(Service.createSessionFromService(service, (GregorianCalendar.SUNDAY - dayOfWeek), weekIndex, realDayOfWeek));
		}
		if (service.reoccurrenceData.mon && GregorianCalendar.MONDAY >= dayOfWeek) {
			service.sessions.add(Service.createSessionFromService(service, (GregorianCalendar.MONDAY - dayOfWeek), weekIndex, realDayOfWeek));
		}
		if (service.reoccurrenceData.tue && GregorianCalendar.TUESDAY >= dayOfWeek) {
			service.sessions.add(Service.createSessionFromService(service, (GregorianCalendar.TUESDAY - dayOfWeek), weekIndex, realDayOfWeek));
		}
		if (service.reoccurrenceData.wed && GregorianCalendar.WEDNESDAY >= dayOfWeek) {
			service.sessions.add(Service.createSessionFromService(service, (GregorianCalendar.WEDNESDAY - dayOfWeek), weekIndex, realDayOfWeek));
		}
		if (service.reoccurrenceData.thu && GregorianCalendar.THURSDAY >= dayOfWeek) {
			service.sessions.add(Service.createSessionFromService(service, (GregorianCalendar.THURSDAY - dayOfWeek), weekIndex, realDayOfWeek));
		}
		if (service.reoccurrenceData.fri && GregorianCalendar.FRIDAY >= dayOfWeek) {
			service.sessions.add(Service.createSessionFromService(service, (GregorianCalendar.FRIDAY - dayOfWeek), weekIndex, realDayOfWeek));
		}
		if (service.reoccurrenceData.sat && GregorianCalendar.SATURDAY >= dayOfWeek) {
			service.sessions.add(Service.createSessionFromService(service, (GregorianCalendar.SATURDAY - dayOfWeek), weekIndex, realDayOfWeek));
		}
	}
	
}